package com.zxjbyte.yiyi.framework.redis.lock.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.google.common.collect.Lists;
import com.zxjbyte.yiyi.framework.common.constant.RedisKeyDefine;
import com.zxjbyte.yiyi.framework.common.constant.StringPool;
import com.zxjbyte.yiyi.framework.common.core.spel.YiyiExpressionEvaluator;
import com.zxjbyte.yiyi.framework.common.exception.ServiceException;
import com.zxjbyte.yiyi.framework.redis.lock.RLockInfo;
import com.zxjbyte.yiyi.framework.redis.lock.RLockTemplate;
import com.zxjbyte.yiyi.framework.redis.lock.annotation.RLock;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;

import java.lang.reflect.Method;
import java.util.List;

/**
 * redis分布式锁 切面
 * @Author zhangxingjia
 * @Date 2023/4/14 17:47
 * @Version: 1.0
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class RLockAspect {

    private static final YiyiExpressionEvaluator EXPRESSION_EVALUATOR = new YiyiExpressionEvaluator();
    private final RLockTemplate lockTemplate;

    @Around("@annotation(rLock)")
    public Object aroundRedisLock(ProceedingJoinPoint joinPoint, RLock rLock) throws Throwable {
        RLockInfo lockInfo = null;
        try {
            List<String> parts = Lists.newArrayList();
            if (StrUtil.isBlank(rLock.key())) {
                Class<?> clazz = joinPoint.getTarget().getClass();
                String methodName = joinPoint.getSignature().getName();
                parts.add(clazz.getSimpleName());
                parts.add(methodName);
            }else{
                parts.add(rLock.key());
            }
            if(ArrayUtil.isNotEmpty(rLock.params())){
                String spElParamKey = getSpElParamKey(joinPoint, rLock.params());
                parts.add(spElParamKey);
            }
            String key = RedisKeyDefine.buildKey(parts);
            lockInfo = lockTemplate.lock(key, rLock.waitTime(), rLock.leaseTime(), rLock.timeUnit(), rLock.type());
            if (ObjectUtil.isNotNull(lockInfo)) {
                return joinPoint.proceed();
            }
            log.warn("@RLock lock fail, lockKey: [{}] ,type: [{}]", key, rLock.type());
            throw new ServiceException("请求太快啦，请稍后再试~");
        }finally {
            if (null != lockInfo && rLock.autoRelease()) {
                final boolean releaseLock = lockTemplate.releaseLock(lockInfo);
                if (!releaseLock) {
                    log.error("@RLock releaseLock fail ,lockKey: [{}] ,type: [{}]", lockInfo.getLockKey(),
                            rLock.type());
                }
            }
        }
    }

    /**
     * 获取参数key
     *
     * @param point     ProceedingJoinPoint
     * @param params     param
     * @return
     */
    private String getSpElParamKey(ProceedingJoinPoint point, String[] params) {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        Object[] args = point.getArgs();
        Object target = point.getTarget();
        Class<?> targetClass = target.getClass();
        ApplicationContext applicationContext = SpringUtil.getApplicationContext();
        EvaluationContext context = EXPRESSION_EVALUATOR.createContext(method, args, target, targetClass, applicationContext);
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
        List<String> paramKeyList = Lists.newArrayListWithCapacity(params.length);
        for(String param : params){
            paramKeyList.add(EXPRESSION_EVALUATOR.evalAsText(param, elementKey, context));
        }
        return CollUtil.join(paramKeyList, StringPool.HASH);
    }
}
